github.com/quinndk/ethereum_read@v0.0.0-20181211143958-29c55eec3237/Docs/0x09 ethash和clique共识引擎.md (about) 1 # 0x09 ethash和clique共识引擎 2 3 # Consensus以太坊共识引擎 4 5 之前讲了以太坊的挖矿逻辑,还没真正涉及到POW的实现。上面也讲到过以太坊的共识引擎有两种,ethash和clique。ethash本质上就是一种POW算法,clique则是POA(ProofOfAuthortiy)算法。目前以太坊采用的是ethash共识引擎,也就是POW算法。今天分别看下两种共识算法的源码实现。 6 7 # Ethash算法 8 9 ### 原理 10 11 抛开其内部实现哈希的算法,其基本原理可以用一个公式表示: 12 13 > #### RAND(h, nonce) <= M / d 14 15 这里h表示区块头的哈希,nonce表示一个自增的变量,RAND表示经过一系列算法生成一个类似随机数的函数。 16 M表示一个极大的数,d则是当前区块的难度值header.diffculty。 17 18 但是涉及到ethash内部哈希值的计算方式就不得不提以下几个概念。 19 20 #### DAG 21 说到这想起一个题外话,之前看到网上有人问比特币矿机能用来挖以太币吗。怎么说呢,有钱任性的话也是可以的,因为这样做的结果是入不敷出。这是为什么呢? 22 23 Bitcoin的POW是完全基于算力的,而Ethash则是基于内存和算力的,它和计算机的内存大小和内存带宽正相关。计算能力再强,它每次读取内存的带宽是有限的,这就是为什么即使用来昂贵的ASIC矿机来挖以太币,收益也不会比PC号多少。但是 ,道高一尺魔高一丈,据说比特大陆已经研发出用于以太坊的专业矿机,不过价格不菲每台800美元。题外话就说到这,接着回归正题。 24 25 我们知道POW算法依赖一个nonce值输入算法来得到一个低于困难度阈值的哈希值,Eth引入了**[DAG](https://github.com/ethereum/wiki/wiki/Ethash-DAG)**来提供一个大的,瞬态,依赖于块头哈希和Nonce随机生成的固定资源的子集来参与最终哈希值的计算。 26 27 DAG资源大约占用1GB大小的内存。 其文件存储路径为: 28 29 - Mac/Linux:$(HOME)/.ethash/full-R<REVISION>-<SEEDHASH> 30 31 - Windows: $(HOME)/Appdata/Local/Ethash/full-R<REVISION>-<SEEDHASH> 32 33 其文件目录命名的意义为: 34 35 - <REVISION>:一个十进制整数,当前的修订版本 36 - <SEEDHASH> :16个小写的十六进制数字,指定了当前epoch(在以太坊中每30000个区块会生成一个DAG,这个DAG被称为epoch,大约5.2d,125h)种子哈希的前8个字节 37 38 DAG文件的内部格式: 39 40 - 1.little-endian小端模式存储(每个unint32以little-endian格式编码) 41 42 - 2.headerBytes:8-byte magic number(0xfee1deadbaddcafe) 43 44 - 3.DAG是一个uint32s类型的二维数组,维度是N * 16,N是一个幻数(从16777186开始) 45 46 - 4.DAG的行应按顺序写入文件,行之间没有分隔符 47 48 下图便是我Mac上的几个DAG文件: 49 50 ![DAG路径](https://upload-images.jianshu.io/upload_images/830585-43a196f0aef1003e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 51 52 ethash依赖于DAG实现POW,从[mining的wiki](https://github.com/ethereum/wiki/wiki/Mining#ethash-dag)中得知:DAG需要很长时间才能生成。如果客户端仅按需生成它,可能会在找到新纪元的第一个块之前看到每个纪元转换的漫长等待。一般会预先计算DAG来避免在每个epoch过渡时发生过长的等待时间。geth执行自动的DAG生成,每次维持两个DAG来保障epoch过渡流畅。 53 54 #### Dagger-Hashimoto 55 ethash算法,又叫Dashimoto (Dagger-Hashimoto),是Hashimoto算法结合Dagger之后产成的一个变种。其实,在最开始以太坊使用的POW就叫[Dagger-Hashimoto](https://github.com/ethereum/wiki/wiki/Dagger-Hashimoto)。后来,进一步改进后便产生了被命名为[ethash](https://github.com/ethereum/wiki/wiki/Ethash)的Dashimoto算法最新版本。这里暂且以最新的ethash为例去分析,旧版本有兴趣的可以自己看看。 56 57 58 [ethash设计的初衷](https://github.com/ethereum/wiki/wiki/Ethash-Design-Rationale)旨在实现以下目标: 59 60 - __1.IO饱和度__:算法应消耗几乎所有可用的内存访问带宽(有效的抵抗ASIC) 61 62 - __2.GPU友好__:尽可能地使用GPU进行挖矿。 63 64 - __3.轻客户端可验证性__:轻客户端应该能够在0.01秒内验证一轮挖掘的有效性,Python或Javascript中验证不到0.1秒,最多1 MB内存(但指数增加) 65 66 - __4.轻客户端减速__:使用轻客户端运行算法的过程应该比使用完整客户端的过程慢得多,以至于轻客户端算法不是经济上可行的实现挖掘实现的途径,包括通过专用硬件 67 68 - __5.轻客户端快速启动__:轻客户端应该能够完全运行并能够在40秒内在Javascript中验证块 69 70 ethash的大概流程是这样的: 71 72 - __1.先根据block number以及block header计算出一个种子值seed__ 73 74 - 2.__使用seed产生一个16MB的伪随机数集cache__ 75 76 - 3.__根据cache生成一个1GB的数据集DAG(可以根据cache快速定位DAG中指定位置的元素,所以一般轻客户端保存cache,完整客户端保存DAG)__ 77 78 - 4.__从DAG中随机选择元素对其进行hash,然后判断哈希值是否小于给定值__ 79 80 - 5.__cache和DAG每个周期(1000个块)更新一次。DAG从1GB开始随着时间线性增长,现在好像达到20多GB了__ 81 82 ### 源码撸起来 83 84 [wiki](https://github.com/ethereum/wiki/wiki/Ethash)里关于ethash代码的分析是以Python为例的,这里我们看官方go-ethereum里的源码实现。 85 86 ``` 87 // Ethash is a consensus engine based on proof-of-work implementing the ethash 88 // algorithm. 89 type Ethash struct { 90 91 // ethash配置 92 config Config 93 94 // 内存缓存,可反复使用避免再生太频繁 95 caches *lru // In memory caches to avoid regenerating too often 96 // 内存数据集 97 datasets *lru // In memory datasets to avoid regenerating too often 98 99 // Mining related fields 100 // 随机工具,用来生成种子 101 rand *rand.Rand // Properly seeded random source for nonces 102 // 挖矿的线程数 103 threads int // Number of threads to mine on if mining 104 // 挖矿通道 105 update chan struct{} // Notification channel to update mining parameters 106 // 平均哈希率 107 hashrate metrics.Meter // Meter tracking the average hashrate 108 109 // The fields below are hooks for testing 110 // 共享pow,无法再生缓存 111 shared *Ethash // Shared PoW verifier to avoid cache regeneration 112 // 未通过pow的区块号,包括fakeMode 113 fakeFail uint64 // Block number which fails PoW check even in fake mode 114 // 验证工作返回消息前的延迟时间 115 fakeDelay time.Duration // Time delay to sleep for before returning from verify 116 117 // 同步锁 118 lock sync.Mutex // Ensures thread safety for the in-memory caches and mining fields 119 } 120 121 // New creates a full sized ethash PoW scheme. 122 // 生成ethash对象 123 func New(config Config) *Ethash { 124 if config.CachesInMem <= 0 { 125 log.Warn("One ethash cache must always be in memory", "requested", config.CachesInMem) 126 config.CachesInMem = 1 127 } 128 if config.CacheDir != "" && config.CachesOnDisk > 0 { 129 log.Info("Disk storage enabled for ethash caches", "dir", config.CacheDir, "count", config.CachesOnDisk) 130 } 131 if config.DatasetDir != "" && config.DatasetsOnDisk > 0 { 132 log.Info("Disk storage enabled for ethash DAGs", "dir", config.DatasetDir, "count", config.DatasetsOnDisk) 133 } 134 return &Ethash{ 135 config: config, 136 caches: newlru("cache", config.CachesInMem, newCache), 137 datasets: newlru("dataset", config.DatasetsInMem, newDataset), 138 update: make(chan struct{}), 139 hashrate: metrics.NewMeter(), 140 } 141 } 142 ``` 143 144 ### generateDataset-DAG生成 145 这里涉及到两个存储数据的类cache和dataset,他们的数据结构类似。我猜想这里的cache对应ethash需要的缓存cache,dataset对应相应的DAG。 146 147 ``` 148 // cache wraps an ethash cache with some metadata to allow easier concurrent use. 149 // cache使用一些元数据包装ethash缓存,以便更容易并发使用。 150 type cache struct { 151 // 属于哪一个epoch 152 epoch uint64 // Epoch for which this cache is relevant 153 // 该内存存储于磁盘的文件对象 154 dump *os.File // File descriptor of the memory mapped cache 155 // 内存映射 156 mmap mmap.MMap // Memory map itself to unmap before releasing 157 // 实际使用的内存 158 cache []uint32 // The actual cache data content (may be memory mapped) 159 once sync.Once // Ensures the cache is generated only once 160 } 161 ... 162 // dataset wraps an ethash dataset with some metadata to allow easier concurrent use. 163 type dataset struct { 164 epoch uint64 // Epoch for which this cache is relevant 165 dump *os.File // File descriptor of the memory mapped cache 166 mmap mmap.MMap // Memory map itself to unmap before releasing 167 dataset []uint32 // The actual cache data content 168 once sync.Once // Ensures the cache is generated only once 169 } 170 ``` 171 172 上面说了,在计算块头和nonce的哈希时需要用到DAG数据集。这时,会首先从磁盘检索对应需要的DAG文件,有则加载,没有的话就新建一个DAG数据集。 173 174 ``` 175 // dataset tries to retrieve a mining dataset for the specified block number 176 // by first checking against a list of in-memory datasets, then against DAGs 177 // stored on disk, and finally generating one if none can be found. 178 // 在磁盘上找到一个DAG,如果没有则创建 179 func (ethash *Ethash) dataset(block uint64) *dataset { 180 // 计算当前对应的epoch 181 epoch := block / epochLength 182 currentI, futureI := ethash.datasets.get(epoch) 183 current := currentI.(*dataset) 184 185 // Wait for generation finish. 186 // 这里有的话会直接加载,没有的话才会真的创建 详见generate代码 187 current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest) 188 189 // If we need a new future dataset, now's a good time to regenerate it. 190 // 创建一个将来的DAG以保障epoch过渡流畅 191 if futureI != nil { 192 future := futureI.(*dataset) 193 go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest) 194 } 195 196 return current 197 } 198 ``` 199 200 这里有个常量epochLength,我们先来看下有关它的定义。因为后面源码用到了许多类似的常量,我们必须清楚他们表示的意义。 201 202 ``` 203 const ( 204 // 初始dataset的字节数 1GB 205 datasetInitBytes = 1 << 30 // Bytes in dataset at genesis 206 // 每epoch dataset数据集的增长量 207 datasetGrowthBytes = 1 << 23 // Dataset growth per epoch 208 // 初始cache的字节数 16MB 209 cacheInitBytes = 1 << 24 // Bytes in cache at genesis 210 // 每epoch cache的字节数增长 211 cacheGrowthBytes = 1 << 17 // Cache growth per epoch 212 // 每个epoch包含的区块数 213 epochLength = 30000 // Blocks per epoch 214 // mix位宽 215 mixBytes = 128 // Width of mix 216 // hash长度 217 hashBytes = 64 // Hash length in bytes 218 // 散列中的32位无符号整数的个数 DAG是个uint32s类型的二维数组,每个uint32s的元素个数为hashWords 219 hashWords = 16 // Number of 32 bit ints in a hash 220 // 每个数据集元素datasetItem的父数 从datasetParents个伪随机选择的缓存数据得到一个datasetItem 221 datasetParents = 256 // Number of parents of each dataset element 222 // 一次cache生成的循环次数 223 cacheRounds = 3 // Number of rounds in cache production 224 // hashimoto循环中的访问次数 225 loopAccesses = 64 // Number of accesses in hashimoto loop 226 ) 227 ``` 228 229 接着深入到generate函数,这里也可以从代码中看到它在磁盘里的存储路径。 230 231 ``` 232 // generate ensures that the dataset content is generated before use. 233 // 确保在使用之前生成DAG数据集 234 func (d *dataset) generate(dir string, limit int, test bool) { 235 d.once.Do(func() { 236 237 //cache和dataset集合(DAG)大小计算 238 csize := cacheSize(d.epoch*epochLength + 1) 239 dsize := datasetSize(d.epoch*epochLength + 1) 240 seed := seedHash(d.epoch*epochLength + 1) 241 if test { 242 csize = 1024 243 dsize = 32 * 1024 244 } 245 // If we don't store anything on disk, generate and return 246 // 目前DAG目录里还不存在DAG文件,则根据cache创建DAG 247 if dir == "" { 248 cache := make([]uint32, csize/4) 249 generateCache(cache, d.epoch, seed) 250 251 d.dataset = make([]uint32, dsize/4) 252 generateDataset(d.dataset, d.epoch, cache) 253 } 254 // Disk storage is needed, this will get fancy 255 // 需要磁盘存储 256 var endian string 257 if !isLittleEndian() { 258 endian = ".be" 259 } 260 path := filepath.Join(dir, fmt.Sprintf("full-R%d-%x%s", algorithmRevision, seed[:8], endian)) 261 logger := log.New("epoch", d.epoch) 262 263 // We're about to mmap the file, ensure that the mapping is cleaned up when the 264 // cache becomes unused. 265 runtime.SetFinalizer(d, (*dataset).finalizer) 266 267 // Try to load the file from disk and memory map it 268 // 加载DAG文件并将内存映射到它 269 var err error 270 d.dump, d.mmap, d.dataset, err = memoryMap(path) 271 if err == nil { 272 logger.Debug("Loaded old ethash dataset from disk") 273 return 274 } 275 logger.Debug("Failed to load old ethash dataset", "err", err) 276 277 // No previous dataset available, create a new dataset file to fill 278 // 没有以前的可用数据集,创建要填充的数据集文件 279 cache := make([]uint32, csize/4) 280 generateCache(cache, d.epoch, seed) 281 282 d.dump, d.mmap, d.dataset, err = memoryMapAndGenerate(path, dsize, func(buffer []uint32) { generateDataset(buffer, d.epoch, cache) }) 283 if err != nil { 284 logger.Error("Failed to generate mapped ethash dataset", "err", err) 285 286 d.dataset = make([]uint32, dsize/2) 287 generateDataset(d.dataset, d.epoch, cache) 288 } 289 // Iterate over all previous instances and delete old ones 290 // 迭代更新DAG文件并删除过于老旧的 291 for ep := int(d.epoch) - limit; ep >= 0; ep-- { 292 seed := seedHash(uint64(ep)*epochLength + 1) 293 path := filepath.Join(dir, fmt.Sprintf("full-R%d-%x%s", algorithmRevision, seed[:8], endian)) 294 os.Remove(path) 295 } 296 }) 297 } 298 ``` 299 300 那一个DAG集合具体是怎么生成的呢,这就要看generateDataset的内部代码了。 301 302 ``` 303 // generateDataset generates the entire ethash dataset for mining. 304 // This method places the result into dest in machine byte order. 305 // 创建用于挖掘的DAG数据集,将结果按机器字节顺序存放到dest 306 func generateDataset(dest []uint32, epoch uint64, cache []uint32) { 307 // Print some debug logs to allow analysis on low end devices 308 logger := log.New("epoch", epoch) 309 310 start := time.Now() 311 defer func() { 312 elapsed := time.Since(start) 313 314 logFn := logger.Debug 315 if elapsed > 3*time.Second { 316 logFn = logger.Info 317 } 318 logFn("Generated ethash verification cache", "elapsed", common.PrettyDuration(elapsed)) 319 }() 320 321 // Figure out whether the bytes need to be swapped for the machine 322 // 判断是否是小端模式 323 swapped := !isLittleEndian() 324 325 // Convert our destination slice to a byte buffer 326 // 将dest转化为字节缓冲区dataset 327 header := *(*reflect.SliceHeader)(unsafe.Pointer(&dest)) 328 header.Len *= 4 329 header.Cap *= 4 330 dataset := *(*[]byte)(unsafe.Pointer(&header)) 331 332 // Generate the dataset on many goroutines since it takes a while 333 // 我们知道一个dataset庞大到需要占用月1GB空间 334 // 所以这里为了加快创建速度,开启多协程来创建它 335 336 // 协程数 337 threads := runtime.NumCPU() 338 size := uint64(len(dataset)) 339 340 // 创建一个等待信号量,目的是为了等所有协程执行完毕再结束主协程 341 var pend sync.WaitGroup 342 pend.Add(threads) 343 344 var progress uint32 345 for i := 0; i < threads; i++ { 346 go func(id int) { 347 defer pend.Done() 348 349 // Create a hasher to reuse between invocations 350 // 一个在调用之间重用的哈希 351 keccak512 := makeHasher(sha3.NewKeccak512()) 352 353 // Calculate the data segment this thread should generate 354 // 计算当前协程应该计算的数据量 355 batch := uint32((size + hashBytes*uint64(threads) - 1) / (hashBytes * uint64(threads))) 356 first := uint32(id) * batch 357 limit := first + batch 358 if limit > uint32(size/hashBytes) { 359 limit = uint32(size / hashBytes) 360 } 361 // Calculate the dataset segment 362 // 计算dataset数据 363 percent := uint32(size / hashBytes / 100) 364 for index := first; index < limit; index++ { 365 366 // dataset数据集由若干个datasetItem组成 367 item := generateDatasetItem(cache, index, keccak512) 368 369 // 字节序反转,以保证最后按小端模式存储 370 if swapped { 371 swap(item) 372 } 373 //将计算好的一个datasetItem添加到dataset 374 copy(dataset[index*hashBytes:], item) 375 376 if status := atomic.AddUint32(&progress, 1); status%percent == 0 { 377 logger.Info("Generating DAG in progress", "percentage", uint64(status*100)/(size/hashBytes), "elapsed", common.PrettyDuration(time.Since(start))) 378 } 379 } 380 }(i) 381 } 382 // Wait for all the generators to finish and return 383 pend.Wait() 384 } 385 ... 386 // generateDatasetItem combines data from 256 pseudorandomly selected cache nodes, 387 // and hashes that to compute a single dataset node. 388 // 组合来自256个伪随机选择的缓存节点的数据,用于计算单个数据集节点的哈希值 389 func generateDatasetItem(cache []uint32, index uint32, keccak512 hasher) []byte { 390 // Calculate the number of theoretical rows (we use one buffer nonetheless) 391 // 计算理论行数 392 rows := uint32(len(cache) / hashWords) 393 394 // Initialize the mix 395 mix := make([]byte, hashBytes) 396 397 binary.LittleEndian.PutUint32(mix, cache[(index%rows)*hashWords]^index) 398 for i := 1; i < hashWords; i++ { 399 binary.LittleEndian.PutUint32(mix[i*4:], cache[(index%rows)*hashWords+uint32(i)]) 400 } 401 keccak512(mix, mix) 402 403 // Convert the mix to uint32s to avoid constant bit shifting 404 // 将mix转换为uint32类型,前面讲过DAG是一个uint32s类型的二维数组 405 intMix := make([]uint32, hashWords) 406 for i := 0; i < len(intMix); i++ { 407 intMix[i] = binary.LittleEndian.Uint32(mix[i*4:]) 408 } 409 // fnv it with a lot of random cache nodes based on index 410 // fnv运算从256个伪随机选择的缓存节点的数据得到一个datasetItem的值 411 for i := uint32(0); i < datasetParents; i++ { 412 parent := fnv(index^i, intMix[i%16]) % rows 413 fnvHash(intMix, cache[parent*hashWords:]) 414 } 415 // Flatten the uint32 mix into a binary one and return 416 // uint32转化成二进制小端格式 417 for i, val := range intMix { 418 binary.LittleEndian.PutUint32(mix[i*4:], val) 419 } 420 keccak512(mix, mix) 421 return mix 422 } 423 ``` 424 425 ### Seal()实现ethash 426 427 我们上面分析挖矿逻辑时,分析到了agent内部是通过Engine.seal来实现挖矿的。这里我们就可以来看看ethash的Seal()实现。 428 429 ``` 430 // Seal implements consensus.Engine, attempting to find a nonce that satisfies 431 // the block's difficulty requirements. 432 // Seal实现了共识引擎,找到满足块的难度要求的Nonce值。 433 func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { 434 // If we're running a fake PoW, simply return a 0 nonce immediately 435 // ModeFake模式立即返回 436 if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake { 437 header := block.Header() 438 header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{} 439 return block.WithSeal(header), nil 440 } 441 // If we're running a shared PoW, delegate sealing to it 442 // 共享模式,转到它的共享对象执行Seal操作 443 if ethash.shared != nil { 444 return ethash.shared.Seal(chain, block, stop) 445 } 446 // Create a runner and the multiple search threads it directs 447 // 创建runner和它指挥的多重搜索线程 448 abort := make(chan struct{}) 449 found := make(chan *types.Block) 450 451 // 线程锁 452 ethash.lock.Lock() 453 // 获取挖矿线程 454 threads := ethash.threads 455 if ethash.rand == nil { 456 // 获取种子seed 457 seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) 458 if err != nil { 459 ethash.lock.Unlock() 460 return nil, err 461 } 462 // 执行成功,拿到合法种子seed,通过其获得rand对象,赋值 463 ethash.rand = rand.New(rand.NewSource(seed.Int64())) 464 } 465 ethash.lock.Unlock() 466 if threads == 0 { 467 // 如果设定的线程数为0,则实际线程数同CPU数 468 threads = runtime.NumCPU() 469 } 470 if threads < 0 { 471 // 允许在本地/远程周围禁用本地挖掘而无需额外的逻辑 472 threads = 0 // Allows disabling local mining without extra logic around local/remote 473 } 474 475 // 创建一个计数的信号量 476 var pend sync.WaitGroup 477 for i := 0; i < threads; i++ { 478 //信号量赋值 479 pend.Add(1) 480 go func(id int, nonce uint64) { 481 // 信号量值减1 482 defer pend.Done() 483 // 挖矿工作 484 ethash.mine(block, id, nonce, abort, found) 485 }(i, uint64(ethash.rand.Int63())) 486 } 487 // Wait until sealing is terminated or a nonce is found 488 // 一直等到找到符合条件的nonce值 489 var result *types.Block 490 select { 491 case <-stop: 492 // Outside abort, stop all miner threads 493 // 停止信号 494 close(abort) 495 case result = <-found: 496 // One of the threads found a block, abort all others 497 // 其中有线程找到了合法区块 498 close(abort) 499 case <-ethash.update: 500 // Thread count was changed on user request, restart 501 // 重启信号 502 close(abort) 503 pend.Wait() 504 return ethash.Seal(chain, block, stop) 505 } 506 // Wait for all miners to terminate and return the block 507 // 等待所有矿工终止并返回该区块 508 // Wait判断信号量计数器大于0,就会阻塞 509 pend.Wait() 510 return result, nil 511 } 512 513 // mine is the actual proof-of-work miner that searches for a nonce starting from 514 // seed that results in correct final block difficulty. 515 // 实际的POW,从种子开始搜索一个nonce,直到正确的合法区块出现 516 func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) { 517 // Extract some data from the header 518 // 从区块头中取出一些数据 519 var ( 520 header = block.Header() 521 hash = header.HashNoNonce().Bytes() 522 target = new(big.Int).Div(maxUint256, header.Difficulty) 523 number = header.Number.Uint64() 524 dataset = ethash.dataset(number) 525 ) 526 // Start generating random nonces until we abort or find a good one 527 // 开始生成随机的随机数,直到我们中止或找到一个合法的 528 var ( 529 // 初始化一个变量来表示尝试次数 530 attempts = int64(0) 531 // 初始化nonce值,后面该值会递增 532 nonce = seed 533 ) 534 logger := log.New("miner", id) 535 logger.Trace("Started ethash search for new nonces", "seed", seed) 536 search: 537 for { 538 select { 539 case <-abort: 540 // Mining terminated, update stats and abort 541 // 停止信号 542 logger.Trace("Ethash nonce search aborted", "attempts", nonce-seed) 543 ethash.hashrate.Mark(attempts) 544 break search 545 546 default: 547 // We don't have to update hash rate on every nonce, so update after after 2^X nonces 548 // 不必更新每个nonce的哈希率,所以在2 ^ X nonces之后更新 549 attempts++ 550 if (attempts % (1 << 15)) == 0 { 551 // 尝试次数达到2^15,更新hashrate 552 ethash.hashrate.Mark(attempts) 553 attempts = 0 554 } 555 // Compute the PoW value of this nonce 556 // 计算当前nonce的pow值 557 digest, result := hashimotoFull(dataset.dataset, hash, nonce) 558 if new(big.Int).SetBytes(result).Cmp(target) <= 0 { 559 // Correct nonce found, create a new header with it 560 // 找到合法的nonce值,为header赋值 561 header = types.CopyHeader(header) 562 header.Nonce = types.EncodeNonce(nonce) 563 header.MixDigest = common.BytesToHash(digest) 564 565 // Seal and return a block (if still needed) 566 select { 567 case found <- block.WithSeal(header): 568 logger.Trace("Ethash nonce found and reported", "attempts", nonce-seed, "nonce", nonce) 569 case <-abort: 570 logger.Trace("Ethash nonce found but discarded", "attempts", nonce-seed, "nonce", nonce) 571 } 572 break search 573 } 574 nonce++ 575 } 576 } 577 // Datasets are unmapped in a finalizer. Ensure that the dataset stays live 578 // during sealing so it's not unmapped while being read. 579 runtime.KeepAlive(dataset) 580 } 581 ``` 582 583 在mine()中hashimotoFull()是为nonce值计算pow的算法,我们继续深入到这个函数中。在它的附近我们发现一个hashimotoLight()函数,这两个函数的入参结构相同,内部代码的处理逻辑也相同。不同的是,前者传的是cache数据集,后者是dataset数据集,再结合名字可以猜测hashimotoLight是hashimotoFull的轻量级实现。 584 585 ``` 586 // hashimotoFull aggregates data from the full dataset (using the full in-memory 587 // dataset) in order to produce our final value for a particular header hash and 588 // nonce. 589 // 在传入的数据集中通过hash和nonce值计算加密值 590 func hashimotoFull(dataset []uint32, hash []byte, nonce uint64) ([]byte, []byte) { 591 // 定义一个lookup函数,用于在数据集中查找数据 592 lookup := func(index uint32) []uint32 { 593 offset := index * hashWords 594 return dataset[offset : offset+hashWords] 595 } 596 597 // 将原始数据集进行了读取分割,然后传给hashimoto函数 598 return hashimoto(hash, nonce, uint64(len(dataset))*4, lookup) 599 } 600 ... 601 // hashimoto aggregates data from the full dataset in order to produce our final 602 // value for a particular header hash and nonce. 603 // 在传入的数据集中通过hash和nonce值计算加密值 604 func hashimoto(hash []byte, nonce uint64, size uint64, lookup func(index uint32) []uint32) ([]byte, []byte) { 605 // Calculate the number of theoretical rows (we use one buffer nonetheless) 606 // 计算数据集理论的行数 607 rows := uint32(size / mixBytes) 608 609 // Combine header+nonce into a 64 byte seed 610 // 合并header和nonce到一个40bytes的seed 611 seed := make([]byte, 40) 612 copy(seed, hash) 613 binary.LittleEndian.PutUint64(seed[32:], nonce) 614 615 // 将seed进行Keccak512加密 616 seed = crypto.Keccak512(seed) 617 // 从seed中获取区块头 618 seedHead := binary.LittleEndian.Uint32(seed) 619 620 // Start the mix with replicated seed 621 // 开始与重复seed的混合 mixBytes/4 = 128/4=32 622 // 长度32,元素uint32 mix占4*32=128bytes 623 mix := make([]uint32, mixBytes/4) 624 for i := 0; i < len(mix); i++ { 625 mix[i] = binary.LittleEndian.Uint32(seed[i%16*4:]) 626 } 627 // Mix in random dataset nodes 628 // 定义一个temp,与mix结构相同,长度相同 629 temp := make([]uint32, len(mix)) 630 631 for i := 0; i < loopAccesses; i++ { 632 parent := fnv(uint32(i)^seedHead, mix[i%len(mix)]) % rows 633 for j := uint32(0); j < mixBytes/hashBytes; j++ { 634 copy(temp[j*hashWords:], lookup(2*parent+j)) 635 } 636 // 将mix中所有元素都与temp中对应位置的元素进行FNV hash运算 637 fnvHash(mix, temp) 638 } 639 // Compress mix 640 // 对Mix进行混淆 641 for i := 0; i < len(mix); i += 4 { 642 mix[i/4] = fnv(fnv(fnv(mix[i], mix[i+1]), mix[i+2]), mix[i+3]) 643 } 644 // 保留8个字节有效数据 645 mix = mix[:len(mix)/4] 646 647 // 将长度为8的mix分散到32位的digest中去 648 digest := make([]byte, common.HashLength) 649 for i, val := range mix { 650 binary.LittleEndian.PutUint32(digest[i*4:], val) 651 } 652 return digest, crypto.Keccak256(append(seed, digest...)) 653 } 654 ``` 655 656 在hashimoto函数中涉及到一个fnv函数,这是一个哈谢算法,全名Fowler-Noll-Vo哈希算法,是以三位发明人名字命名的。FNV能快速hash大量数据并保持较小的冲突率,它的高度分散使它适用于hash一些非常相近的字符串,比如URL,hostname,文件名,text,IP地址等。 657 658 ``` 659 // fnv is an algorithm inspired by the FNV hash, which in some cases is used as 660 // a non-associative substitute for XOR. Note that we multiply the prime with 661 // the full 32-bit input, in contrast with the FNV-1 spec which multiplies the 662 // prime with one byte (octet) in turn. 663 // fnv算法 664 func fnv(a, b uint32) uint32 { 665 return a*0x01000193 ^ b 666 } 667 668 // fnvHash mixes in data into mix using the ethash fnv method. 669 // fnv哈希算法 670 func fnvHash(mix []uint32, data []uint32) { 671 for i := 0; i < len(mix); i++ { 672 mix[i] = mix[i]*0x01000193 ^ data[i] 673 } 674 } 675 ``` 676 677 至此就达到计算一个nonce的pow值得目的了,判断noce合法的条件是它小于一个给定的target。我们知道比特币出块的时间是10min左右,以太坊呢是12s左右。那么以太坊内部是怎么保证这个出块时间不会有太大的波动,这就需要来看看CalcDifficulty()方法。 678 679 ``` 680 // CalcDifficulty is the difficulty adjustment algorithm. It returns 681 // the difficulty that a new block should have when created at time 682 // given the parent block's time and difficulty. 683 // 难度调整算法,用来保障出块时间在一个固定值上下波动 684 func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { 685 return CalcDifficulty(chain.Config(), time, parent) 686 } 687 688 // CalcDifficulty is the difficulty adjustment algorithm. It returns 689 // the difficulty that a new block should have when created at time 690 // given the parent block's time and difficulty. 691 // 计算在给定父块时间和难度下当前块应该具有的难度 692 func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int { 693 next := new(big.Int).Add(parent.Number, big1) 694 switch { 695 // 以太坊不同阶段难度调整算法不同 696 case config.IsByzantium(next): 697 return calcDifficultyByzantium(time, parent) 698 case config.IsHomestead(next): 699 return calcDifficultyHomestead(time, parent) 700 default: 701 return calcDifficultyFrontier(time, parent) 702 } 703 } 704 ``` 705 706 目前以太坊处在Homestead阶段,所以就以calcDifficultyHomestead为例来看看内部调整难度的算法。其难度计算规则见[eip-2](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md/)。 707 708 ``` 709 // calcDifficultyHomestead is the difficulty adjustment algorithm. It returns 710 // the difficulty that a new block should have when created at time given the 711 // parent block's time and difficulty. The calculation uses the Homestead rules. 712 // Homestead阶段的区块难度调整算法 713 func calcDifficultyHomestead(time uint64, parent *types.Header) *big.Int { 714 //这里给出了Homestead阶段的当前难度计算规则 715 // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md 716 // algorithm: 717 // diff = (parent_diff + 718 // (parent_diff / 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99)) 719 // ) + 2^(periodCount - 2) 720 // 2^(periodCount - 2)表示指数难度调整组件 721 722 // 当前时间 723 bigTime := new(big.Int).SetUint64(time) 724 // 父块出块时间 725 bigParentTime := new(big.Int).Set(parent.Time) 726 727 // holds intermediate values to make the algo easier to read & audit 728 x := new(big.Int) 729 y := new(big.Int) 730 731 // 1 - (block_timestamp - parent_timestamp) // 10 732 x.Sub(bigTime, bigParentTime) 733 x.Div(x, big10) 734 x.Sub(big1, x) 735 736 // max(1 - (block_timestamp - parent_timestamp) // 10, -99) 737 if x.Cmp(bigMinus99) < 0 { 738 x.Set(bigMinus99) 739 } 740 // (parent_diff + parent_diff // 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99)) 741 y.Div(parent.Difficulty, params.DifficultyBoundDivisor) 742 x.Mul(y, x) 743 x.Add(parent.Difficulty, x) 744 745 // minimum difficulty can ever be (before exponential factor) 746 if x.Cmp(params.MinimumDifficulty) < 0 { 747 x.Set(params.MinimumDifficulty) 748 } 749 // for the exponential factor 750 periodCount := new(big.Int).Add(parent.Number, big1) 751 periodCount.Div(periodCount, expDiffPeriod) 752 753 // the exponential factor, commonly referred to as "the bomb" 754 // diff = diff + 2^(periodCount - 2) 755 if periodCount.Cmp(big1) > 0 { 756 y.Sub(periodCount, big2) 757 y.Exp(big2, y, nil) 758 x.Add(x, y) 759 } 760 return x 761 } 762 ``` 763 764 每次进行挖矿难度的计算是在prepare阶段,prepare函数是实现共识引擎的准备阶段。 765 766 ``` 767 // Prepare implements consensus.Engine, initializing the difficulty field of a 768 // header to conform to the ethash protocol. The changes are done inline. 769 // 实现共识引擎的准备阶段 770 func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header) error { 771 parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) 772 if parent == nil { 773 return consensus.ErrUnknownAncestor 774 } 775 header.Difficulty = ethash.CalcDifficulty(chain, header.Time.Uint64(), parent) 776 return nil 777 } 778 ``` 779 780 至此,ethash算法源码就分析完了。 781 782 # clique算法 783 784 ### 原理 785 Clique算法又称Proof-of-Authortiy(PoA),是以太坊测试网Ropsten在经过一次DDos攻击之后,数家公司共同研究推出的共识引擎,它运行在以太坊测试网Kovan上。 786 787 PoA共识的主要特点: 788 789 - PoA是依靠预设好的授权节点(signers),负责产生block. 790 - 可以由已授权的signer选举(投票超过50%)加入新的signer。 791 - 即使存在恶意signer,他最多只能攻击连续块(数量是 (SIGNER_COUNT / 2) + 1) 中的1个,期间可以由其他signer投票踢出该恶意signer。 792 - 可指定产生block的时间。 793 794 抛开授权节点的选举算法,Clique原理同样可以用一个公式来表示: 795 796 #### n = F(pr, h) 797 798 其中,F()是一个数字签名函数(目前是ECDSA),pr是公钥(common.Address类型),h是被签名的内容(common.Hash类型),n是最后生成的签名(一个65bytes的字符串)。 799 800 在Clique算法中,所有节点被分为两类: 801 802 - 认证节点,类似于矿工节点 803 - 非认证节点, 类似普通的只能同步的节点 804 805 这两种节点的角色可以互换,这种互换是通过对proposal的投票完成的。 806 807 - 任何节点N都可以提交一个Propose来申请成为signer,然后所有的signers集体投票 808 809 - 一个Signer只能给节点N投一张票 810 811 - 一张投票包括:投票节点地址,被投票节点地址,被投票节点认证状态 812 813 - 每进入一个新的epoch,所有之前的pending投票都作废 814 815 PoA的大概流程是这样的: 816 817 - __1.创世区块中指定一组认证节点(signers), signer地址保存在区块Extra字段__ 818 819 - __2.开始mining后,初始指定的signers对block进行签名和广播,签名结果保存在Extra字段__ 820 821 - __3.由于可能发生signers改变,Extra字段更新已授权的signers__ 822 823 - __4.每一个高度上处于IN-TURN状态的signer签名的Block优先广播,OUT-OF-TURN状态的随机延时一段时间再进行广播__ 824 825 - __5.新的signer通过API接口发起proposal,该proposal复用blockHeader的coinbase字段(signerAddress)和nonce字段(0xffffffffffffffff)广播,原有signers对该proposal进行投票,票数过半该发起者成为真正的signer__ 826 827 - __6.如果需要踢出一个signer,所有signers对该踢出行为进行投票,同样票数过半,该signer变成普通节点__ 828 829 这里通过API接口发起proposal时对blockHeader字段的复用情况是这样的: 830 831 - __针对genesisBlock extra字段包含:__ 832 - 32bytes前缀(extraVanity) 833 - 所有认证用户signers地址 834 - 65bytes后缀(extraSeal):用来保存signer签名 835 836 - __针对其他Block:__ 837 - extra: extraVanity和extraSeal 838 - Noce:添加signer(nonceAuthVote: 0xffffffffffffffff);移除signer(nonceDropVote: 0x0000000000000000) 839 - coinbase:被投票节点地址 840 - Difficulty:1--本block签名者(IN-TURN);2-非本block签名者(OUT-OF-TURN) 841 842 ### 开撸源码 843 844 首先还是来看clique共识中几个重要的数据结构。首先看clique自身: 845 846 ``` 847 // Clique is the proof-of-authority consensus engine proposed to support the 848 // Ethereum testnet following the Ropsten attacks. 849 // Clique是在Ropsten攻击之后支持Ethereum testnet的权威证明共识引擎 850 type Clique struct { 851 // 共识引擎配置 852 config *params.CliqueConfig // Consensus engine configuration parameters 853 // 用于存取检索点快照的数据库 854 db ethdb.Database // Database to store and retrieve snapshot checkpoints 855 856 // 最近区块的快照,用于加速快照重组 857 recents *lru.ARCCache // Snapshots for recent block to speed up reorgs 858 // 最近区块的签名,用于加速挖矿 859 signatures *lru.ARCCache // Signatures of recent blocks to speed up mining 860 861 // 当前提出的proposals列表 862 proposals map[common.Address]bool // Current list of proposals we are pushing 863 864 // signer地址 865 signer common.Address // Ethereum address of the signing key 866 // 签名函数 867 signFn SignerFn // Signer function to authorize hashes with 868 // 读写锁 869 lock sync.RWMutex // Protects the signer fields 870 } 871 ``` 872 873 共识引擎配置的结构: 874 875 ``` 876 877 // CliqueConfig is the consensus engine configs for proof-of-authority based sealing. 878 // Clique共识引擎配置 879 type CliqueConfig struct { 880 // 距离上一区块出块后的时间间隔(s) 881 Period uint64 `json:"period"` // Number of seconds between blocks to enforce 882 // 重置投票和检查点的epoch长度 883 Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint 884 } 885 ``` 886 887 snapshot是指定时间点的投票状态。 888 889 ``` 890 // Snapshot is the state of the authorization voting at a given point in time. 891 // 指定时间点的投票状态 892 type Snapshot struct { 893 // clique共识配置 894 config *params.CliqueConfig // Consensus engine parameters to fine tune behavior 895 // 最近区块签名的缓存,为了加速恢复 896 sigcache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover 897 898 // 快照建立的区块号 899 Number uint64 `json:"number"` // Block number where the snapshot was created 900 // 区块hash 901 Hash common.Hash `json:"hash"` // Block hash where the snapshot was created 902 // 当下Signer的集合 903 Signers map[common.Address]struct{} `json:"signers"` // Set of authorized signers at this moment 904 // 最近签名区块地址的集合 905 Recents map[uint64]common.Address `json:"recents"` // Set of recent signers for spam protections 906 // 按顺序排列的投票列表 907 Votes []*Vote `json:"votes"` // List of votes cast in chronological order 908 // 当前投票结果,可以避免重新计算 909 Tally map[common.Address]Tally `json:"tally"` // Current vote tally to avoid recalculating 910 } 911 ``` 912 913 其中,Vote是授权签名者为修改授权列表而进行的单一投票;Tally是Tally是一个简单的投票结果,以保持当前的投票得分。 914 915 ``` 916 // Vote represents a single vote that an authorized signer made to modify the 917 // list of authorizations. 918 // 授权签名者为修改授权列表而进行的单一投票 919 type Vote struct { 920 // 提出投票的signer 921 Signer common.Address `json:"signer"` // Authorized signer that cast this vote 922 // 投票所在的区块编号 923 Block uint64 `json:"block"` // Block number the vote was cast in (expire old votes) 924 // 被投票更改认证状态的地址 925 Address common.Address `json:"address"` // Account being voted on to change its authorization 926 // 是否授权或取消对已投票帐户的授权 927 Authorize bool `json:"authorize"` // Whether to authorize or deauthorize the voted account 928 } 929 930 // Tally is a simple vote tally to keep the current score of votes. Votes that 931 // go against the proposal aren't counted since it's equivalent to not voting. 932 // 一个简单的投票结果,以保持当前的投票得分。 933 type Tally struct { 934 // 投票是关于授权新的signer还是踢掉signer 935 Authorize bool `json:"authorize"` // Whether the vote is about authorizing or kicking someone 936 // 到目前为止希望通过提案的投票数 937 Votes int `json:"votes"` // Number of votes until now wanting to pass the proposal 938 } 939 ``` 940 941 接下来,同样进入worker.agent.engine.seal()代码来看看PoA共识的实现逻辑。 942 943 ``` 944 // Seal implements consensus.Engine, attempting to create a sealed block using 945 // the local signing credentials. 946 // 实现consensus.Engine 947 func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { 948 header := block.Header() 949 950 // Sealing the genesis block is not supported 951 number := header.Number.Uint64() 952 // 当前区块是创世区块 953 if number == 0 { 954 return nil, errUnknownBlock 955 } 956 // For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing) 957 // 不支持0-period的链 958 if c.config.Period == 0 && len(block.Transactions()) == 0 { 959 return nil, errWaitTransactions 960 } 961 // Don't hold the signer fields for the entire sealing procedure 962 // 不要在整个签名过程中持有签名字段 963 c.lock.RLock() 964 // 获取signer和签名方法 965 signer, signFn := c.signer, c.signFn 966 c.lock.RUnlock() 967 968 // Bail out if we're unauthorized to sign a block 969 // 获取快照 970 snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) 971 if err != nil { 972 return nil, err 973 } 974 if _, authorized := snap.Signers[signer]; !authorized { 975 return nil, errUnauthorized 976 } 977 // If we're amongst the recent signers, wait for the next block 978 // 如果是最近的signers中的一员,等待下一个块 979 for seen, recent := range snap.Recents { 980 if recent == signer { 981 // Signer is among recents, only wait if the current block doesn't shift it out 982 // 当前区块没有踢出Signer则继续等待 983 if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit { 984 log.Info("Signed recently, must wait for others") 985 <-stop 986 return nil, nil 987 } 988 } 989 } 990 // Sweet, the protocol permits us to sign the block, wait for our time 991 // 执行到这说明协议允许我们来签名这个区块 992 delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now()) // nolint: gosimple 993 if header.Difficulty.Cmp(diffNoTurn) == 0 { 994 // It's not our turn explicitly to sign, delay it a bit 995 // 当前处于OUT-OF-TURN状态,随机一定时间延迟处理 996 wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime 997 delay += time.Duration(rand.Int63n(int64(wiggle))) 998 999 log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) 1000 } 1001 log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay)) 1002 1003 select { 1004 case <-stop: 1005 // 停止信号 1006 return nil, nil 1007 case <-time.After(delay): 1008 } 1009 // Sign all the things! 1010 // 签名工作 1011 sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes()) 1012 if err != nil { 1013 return nil, err 1014 } 1015 // 更新区块头的extra字段 1016 copy(header.Extra[len(header.Extra)-extraSeal:], sighash) 1017 1018 // 通过区块头组装新区块 1019 return block.WithSeal(header), nil 1020 } 1021 ``` 1022 1023 其中获取投票状态快照的方法为: 1024 1025 ``` 1026 // snapshot retrieves the authorization snapshot at a given point in time. 1027 // 获取投票状态快照 1028 func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) { 1029 // Search for a snapshot in memory or on disk for checkpoints 1030 // 在内存或磁盘上检索一个快照以检查检查点 1031 var ( 1032 // 区块头 1033 headers []*types.Header 1034 // 快照对象 1035 snap *Snapshot 1036 ) 1037 for snap == nil { 1038 // If an in-memory snapshot was found, use that 1039 // 如果一个内存里的快照被找到 1040 if s, ok := c.recents.Get(hash); ok { 1041 snap = s.(*Snapshot) 1042 break 1043 } 1044 // If an on-disk checkpoint snapshot can be found, use that 1045 // 如果一个磁盘检查点的快照被找到 1046 if number%checkpointInterval == 0 { 1047 // 从数据库中加载一个快照 1048 if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil { 1049 log.Trace("Loaded voting snapshot from disk", "number", number, "hash", hash) 1050 snap = s 1051 break 1052 } 1053 } 1054 // If we're at block zero, make a snapshot 1055 // 处于创世区块,创建一个快照 1056 if number == 0 { 1057 genesis := chain.GetHeaderByNumber(0) 1058 if err := c.VerifyHeader(chain, genesis, false); err != nil { 1059 return nil, err 1060 } 1061 signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength) 1062 for i := 0; i < len(signers); i++ { 1063 copy(signers[i][:], genesis.Extra[extraVanity+i*common.AddressLength:]) 1064 } 1065 // 创建新快照 1066 snap = newSnapshot(c.config, c.signatures, 0, genesis.Hash(), signers) 1067 if err := snap.store(c.db); err != nil { 1068 return nil, err 1069 } 1070 log.Trace("Stored genesis voting snapshot to disk") 1071 break 1072 } 1073 // No snapshot for this header, gather the header and move backward 1074 // 没有这个区块头的快照,则收集区块头并向后移动 1075 var header *types.Header 1076 if len(parents) > 0 { 1077 // If we have explicit parents, pick from there (enforced) 1078 // 如果有明确的父块,必须选出一个 1079 header = parents[len(parents)-1] 1080 if header.Hash() != hash || header.Number.Uint64() != number { 1081 return nil, consensus.ErrUnknownAncestor 1082 } 1083 parents = parents[:len(parents)-1] 1084 } else { 1085 // No explicit parents (or no more left), reach out to the database 1086 // 没有明确的父块 1087 header = chain.GetHeader(hash, number) 1088 if header == nil { 1089 return nil, consensus.ErrUnknownAncestor 1090 } 1091 } 1092 headers = append(headers, header) 1093 number, hash = number-1, header.ParentHash 1094 } 1095 // Previous snapshot found, apply any pending headers on top of it 1096 // 找到之前的快照,将所有pending的区块头放在它的前面 1097 for i := 0; i < len(headers)/2; i++ { 1098 headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i] 1099 } 1100 // 通过区块头生成新的快照 1101 snap, err := snap.apply(headers) 1102 if err != nil { 1103 return nil, err 1104 } 1105 // 将当前快照区块的hash存到recents中 1106 c.recents.Add(snap.Hash, snap) 1107 1108 // If we've generated a new checkpoint snapshot, save to disk 1109 // 如果生成了一个新的检查点快照,保存到磁盘上 1110 if snap.Number%checkpointInterval == 0 && len(headers) > 0 { 1111 if err = snap.store(c.db); err != nil { 1112 return nil, err 1113 } 1114 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash) 1115 } 1116 return snap, err 1117 } 1118 ``` 1119 1120 接着继续深入snap.apply函数: 1121 1122 ``` 1123 1124 // apply creates a new authorization snapshot by applying the given headers to 1125 // the original one. 1126 // 根据区块头创建一个新的signer的快照 1127 func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { 1128 // Allow passing in no headers for cleaner code 1129 if len(headers) == 0 { 1130 return s, nil 1131 } 1132 // Sanity check that the headers can be applied 1133 // 对入参区块头做完整性检查 1134 for i := 0; i < len(headers)-1; i++ { 1135 if headers[i+1].Number.Uint64() != headers[i].Number.Uint64()+1 { 1136 return nil, errInvalidVotingChain 1137 } 1138 } 1139 1140 // 判断区块序号是否连续 1141 if headers[0].Number.Uint64() != s.Number+1 { 1142 return nil, errInvalidVotingChain 1143 } 1144 // Iterate through the headers and create a new snapshot 1145 // 遍历区块头数组并建立新的侉子好 1146 snap := s.copy() 1147 1148 for _, header := range headers { 1149 // Remove any votes on checkpoint blocks 1150 // 移除检查点快照上的任何投票 1151 number := header.Number.Uint64() 1152 if number%s.config.Epoch == 0 { 1153 snap.Votes = nil 1154 snap.Tally = make(map[common.Address]Tally) 1155 } 1156 // Delete the oldest signer from the recent list to allow it signing again 1157 // 移除投票票数过半,移除signer 1158 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit { 1159 delete(snap.Recents, number-limit) 1160 } 1161 // Resolve the authorization key and check against signers 1162 // 解析授权密钥并检查签名者 1163 signer, err := ecrecover(header, s.sigcache) 1164 if err != nil { 1165 return nil, err 1166 } 1167 if _, ok := snap.Signers[signer]; !ok { 1168 return nil, errUnauthorized 1169 } 1170 for _, recent := range snap.Recents { 1171 if recent == signer { 1172 return nil, errUnauthorized 1173 } 1174 } 1175 // 记录signer为该区块的签名者 1176 snap.Recents[number] = signer 1177 1178 // Header authorized, discard any previous votes from the signer 1179 // 丢弃之前的投票 1180 for i, vote := range snap.Votes { 1181 if vote.Signer == signer && vote.Address == header.Coinbase { 1182 // Uncast the vote from the cached tally 1183 // 从缓存的计数中取消投票 1184 snap.uncast(vote.Address, vote.Authorize) 1185 1186 // Uncast the vote from the chronological list 1187 // 从时间顺序列表中取消投票 1188 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) 1189 break // only one vote allowed 1190 } 1191 } 1192 // Tally up the new vote from the signer 1193 // 从签名者那里获得新的投票 1194 var authorize bool 1195 switch { 1196 case bytes.Equal(header.Nonce[:], nonceAuthVote): 1197 authorize = true 1198 case bytes.Equal(header.Nonce[:], nonceDropVote): 1199 authorize = false 1200 default: 1201 return nil, errInvalidVote 1202 } 1203 if snap.cast(header.Coinbase, authorize) { 1204 snap.Votes = append(snap.Votes, &Vote{ 1205 Signer: signer, 1206 Block: number, 1207 Address: header.Coinbase, 1208 Authorize: authorize, 1209 }) 1210 } 1211 // If the vote passed, update the list of signers 1212 // 投票通过,更新signers列表 1213 if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 { 1214 1215 // 投票是选举新signer 1216 if tally.Authorize { 1217 snap.Signers[header.Coinbase] = struct{}{} 1218 } else { 1219 // 投票是选移除signer 1220 delete(snap.Signers, header.Coinbase) 1221 1222 // Signer list shrunk, delete any leftover recent caches 1223 // 签名者列表缩小,删除任何剩余的最近缓存 1224 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit { 1225 delete(snap.Recents, number-limit) 1226 } 1227 // Discard any previous votes the deauthorized signer cast 1228 // 放弃任何以前的授权签名者投票 1229 for i := 0; i < len(snap.Votes); i++ { 1230 if snap.Votes[i].Signer == header.Coinbase { 1231 // Uncast the vote from the cached tally 1232 snap.uncast(snap.Votes[i].Address, snap.Votes[i].Authorize) 1233 1234 // Uncast the vote from the chronological list 1235 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) 1236 1237 i-- 1238 } 1239 } 1240 } 1241 // Discard any previous votes around the just changed account 1242 // 放弃已更改授权状态的账户之前的投票 1243 for i := 0; i < len(snap.Votes); i++ { 1244 if snap.Votes[i].Address == header.Coinbase { 1245 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) 1246 i-- 1247 } 1248 } 1249 delete(snap.Tally, header.Coinbase) 1250 } 1251 } 1252 snap.Number += uint64(len(headers)) 1253 snap.Hash = headers[len(headers)-1].Hash() 1254 1255 return snap, nil 1256 } 1257 ``` 1258 1259 综上分析,只有认证节点才有权利出块,其他节点只能同步区块。每次出块时,都会创建一个snapshot快照来表示当前时间的投票状态,这里涉及到了基于投票的认证节点的维护机制。 1260 1261 每次认证节点的改变都是通过api向外暴露的propose接口,然后所有的认证节点signers对该提议propose进行投票,超过半数通过投票,最后更新认证节点signer列表并将认证住状态发生改变的账户之前的投票做相应处理。 1262 1263 同样clique也会涉及到出块时间的控制。 1264 1265 ``` 1266 // CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty 1267 // that a new block should have based on the previous blocks in the chain and the 1268 // current signer. 1269 func (c *Clique) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { 1270 snap, err := c.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil) 1271 if err != nil { 1272 return nil 1273 } 1274 return CalcDifficulty(snap, c.signer) 1275 } 1276 1277 // CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty 1278 // that a new block should have based on the previous blocks in the chain and the 1279 // current signer. 1280 // clique共识难度调整算法 当前块的难度基于前一个块和当前的签名者 1281 func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int { 1282 if snap.inturn(snap.Number+1, signer) { 1283 return new(big.Int).Set(diffInTurn) 1284 } 1285 return new(big.Int).Set(diffNoTurn) 1286 } 1287 ... 1288 // inturn returns if a signer at a given block height is in-turn or not. 1289 // 给定高度块的签名者是否轮流 1290 func (s *Snapshot) inturn(number uint64, signer common.Address) bool { 1291 signers, offset := s.signers(), 0 1292 for offset < len(signers) && signers[offset] != signer { 1293 offset++ 1294 } 1295 return (number % uint64(len(signers))) == uint64(offset) 1296 } 1297 ``` 1298 1299 这里的出块难度实际上就是基于签名者的。如果当前区块的签名者是轮流签名的,那么当前signer可以立即签名一个区块(因为已经轮到这个signer签名了);反之,如果签名者不是轮流的,那么将会随机等待一段时间再签名这个区块(上个区块很可能就是这个singer签名的)。 1300 1301 ``` 1302 func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { 1303 ... 1304 // 执行到这说明协议允许我们来签名这个区块 1305 delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now()) // nolint: gosimple 1306 // 出块难度值判断 1307 if header.Difficulty.Cmp(diffNoTurn) == 0 { 1308 // It's not our turn explicitly to sign, delay it a bit 1309 // 当前处于OUT-OF-TURN状态,随机一定时间延迟处理 1310 wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime 1311 delay += time.Duration(rand.Int63n(int64(wiggle))) 1312 1313 log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) 1314 } 1315 ... 1316 } 1317 ``` 1318 1319 1320 1321 至此,以太坊两种共识引擎clique和ethash的实现源码就分析完毕了。 1322 1323 1324